﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Threading;
using System.IO.IsolatedStorage;
using System.Security.Cryptography;
using System.Text;
using System.Diagnostics;
using Galaktika.BI.Runtime.Services;
using Galaktika.BI.Silverlight.Commands;

namespace Galaktika.BI.Silverlight.Services
{
    /// <summary>
    /// Предоставляет сервис, используемый для вызова серверных команд.
    /// </summary>
    public class SyncDataService : IDisposable
    {
        DataServiceSoapClient m_Client;
        private static bool m_enableClientCaching = false;

        /// <summary>
        /// Предоставляет сервис, используемый для вызова серверных команд.
        /// </summary>
        public SyncDataService()
        {
            m_Client = Galaktika.BI.Runtime.Services.ServiceManagerHelper.CreateService<DataServiceSoapClient, DataServiceSoap>();
            m_Client.InvokeCommandCompleted += new EventHandler<InvokeCommandCompletedEventArgs>(client_InvokeCommandCompleted);
        }

        public static readonly List<Action<InvokeSchema>> BeforeCommandInvokeActions = 
            new List<Action<InvokeSchema>>();
        public static readonly List<Action<InvokeResultSchema, InvokeSchema>> AfterCommandInvokeActions = 
            new List<Action<InvokeResultSchema, InvokeSchema>>();

        public static void EnableCache(Func<string, string> getBinderHash)
        {
            m_enableClientCaching = true;
            m_GetDataBinderStatusHash = getBinderHash;
        }

        public static void DisableCache()
        {
            m_enableClientCaching = false;
            m_GetDataBinderStatusHash = null;
        }

        /// <summary>
        /// Синхронно исполняет команду, описанную в <paramref name="schema"/> на сервере.
        /// Метод должен вызываться <b>только</b> из фонового потока.
        /// </summary>
        /// <param name="schema">Объект, описывающий схему вызова команды.</param>
        /// <exception cref="Galaktika.BI.Runtime.Services.ServerException" />
        /// <returns>Объект, описывающий результат вызова команды.</returns>
        public InvokeResultSchema InvokeCommand(InvokeSchema schema)
        {
            InvokeResultSchema resultSchema = new InvokeResultSchema();
            resultSchema = this.InvokeCommand(schema, null);
            return resultSchema;
        }

        private ManualResetEvent m_Event = new ManualResetEvent(false);
        private InvokeResultSchema m_Res;
        private static Func<string, string> m_GetDataBinderStatusHash;

        /// <summary>
        /// Синхронно исполняет команду, описанную в <paramref name="schema"/> на сервере.
        /// Метод должен вызываться <b>только</b> из фонового потока.
        /// </summary>
        /// <param name="schema">Объект, описывающий схему вызова команды.</param>
        /// <param name="state">
        /// Пользовательский объект, 
        /// доступ к которому необходимо получить в обработчике результата
        /// выполнения команды.
        /// </param>
        /// <exception cref="Galaktika.BI.Runtime.Services.ServerException" />
        /// <returns>Объект, описывающий результат вызова команды.</returns>
        public InvokeResultSchema InvokeCommand(InvokeSchema schema, object state)
        {
            m_Res = new InvokeResultSchema();
            var contextId = schema[KnownInvokeArgs.ContextId];
            if (m_enableClientCaching && !string.IsNullOrEmpty(contextId))
            {
                var binderStatusHash = m_GetDataBinderStatusHash(contextId);
                var hash = string.Format("BIcache{0}{1}", schema.GetSHA1Hash(), binderStatusHash);
                if (ClientCache.Instance[contextId].Data.ContainsKey(hash))
                {
                    m_Res = (InvokeResultSchema)ClientCache.Instance[contextId].Data[hash];
                }
                else
                {
                    BeforeCommandInvokeActions.ForEach(a => a(schema));

                    m_Client.InvokeCommandAsync(schema, state);
                    m_Event.WaitOne();

                    AfterCommandInvokeActions.ForEach(a => a(m_Res, schema));

                    if (IsCacheExpiredCommand(schema))
                    {
                        ClientCache.Instance.Clear();
                    }
                    else
                    {
                        if (this.IsCacheableCommand(schema))
                        {
                            ClientCache.Instance[contextId].Data[hash] = m_Res;
                        }
                        else
                        {
                            ClientCache.Instance.Remove(contextId);
                        }
                    }
                }
            }
            else
            {
                BeforeCommandInvokeActions.ForEach(a => a(schema));

                m_Client.InvokeCommandAsync(schema, state);
                m_Event.WaitOne();

                AfterCommandInvokeActions.ForEach(a => a(m_Res, schema));
            }


            if (this.Error != null)
            {
                throw this.Error;
            }
            if (m_Res.ContentType == KnownContentTypes.Error)
            {
                throw new ServerException(m_Res.Content, null);
            }

            return m_Res;
        }

        private bool IsCacheableCommand(InvokeSchema schema)
        { 
            return 
                schema.CommandId != CommandId.UpdateCubeCommandId &&
                schema.CommandId != CommandId.CommitTransactionCommandId &&
                schema.CommandId != CommandId.RollbackTransactionCommandId;
        }

        private bool IsCacheExpiredCommand(InvokeSchema schema)
        {
            return schema.CommandId == CommandId.CommitTransactionCommandId ||
                schema.CommandId == CommandId.RollbackTransactionCommandId;

        }

        /// <summary>
        /// Возвращает исключение, возникшее при обращении к серверу.
        /// Если исключения не произошло возвращает null.
        /// </summary>
        public Exception Error { get; private set; }

        void client_InvokeCommandCompleted(object sender, InvokeCommandCompletedEventArgs e)
        {
            if (e.Error == null)
            {
                m_Res = e.Result;
            }
            else
            {
                this.Error = e.Error;
                m_Res = new InvokeResultSchema(CommandId.MockCommandId, e.Error.ToString(), KnownContentTypes.Error);
            }
            m_Event.Set();
        }

        #region IDisposable Members

        public void Dispose()
        {
            try
            {
                m_Client.Abort();
            }
            catch
            {
                // doesnt matter
            }
        }

        #endregion
    }

    public class ClientCache
    {
        public class ContextStorage
        {
            public ContextStorage()
            {
                this.Data = new Dictionary<string, object>();
            }
            
            public Dictionary<string, object> Data { get; set; }
        }

        private static IsolatedStorageSettings m_cache = null;

        private ClientCache() {}

        private static ClientCache m_Instance = new ClientCache();
        public static ClientCache Instance
        {
            get
            {
                return m_Instance;
            }
        }

        public ContextStorage this[string contextId]
        {
            get
            {
                if (string.IsNullOrEmpty(contextId))
                {
                    contextId = "__Default";
                }
                ContextStorage storage = null;
                if (ClientCache.Settings.Contains(contextId))
                {
                    storage = (ContextStorage)ClientCache.Settings[contextId];
                }
                else
                {
                    storage = new ContextStorage();
                    ClientCache.Settings[contextId] = storage;
                }

                return storage;
            }
        }

        public void Remove(string contexId)
        {
            Settings.Remove(contexId);
        }

        public void Clear()
        {
            Settings.Clear();
        }

        private static IsolatedStorageSettings Settings
        {
            get
            {
                if (m_cache == null) 
                {
                    lock (m_Instance)
                    {
                        if (m_cache == null)
                            m_cache = IsolatedStorageSettings.ApplicationSettings;
                    }
                }
                return m_cache;
            }
        }
    }
}
